]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
Add a ClutterGesture subclass for starting DND operations
authorJonas Dreßler <verdre@v0yd.nl>
Tue, 7 May 2024 17:07:06 +0000 (19:07 +0200)
committerCarlos Garnacho <carlosg@gnome.org>
Thu, 28 Aug 2025 17:14:06 +0000 (19:14 +0200)
Factor out the starting part of the DND operation from dnd.js and move it
into a new ClutterGesture subclass: ShellDndStartGesture

As with the previous port to ClutterGesture, this new gesture is written
in C due to performance concerns when we'll get rid of motion event
compression and start handling eg. 1000 HZ mouse events at the full rate.

This gesture now integrates with the ClutterGesture state tracking system
(giving us the benefit of coordinating with other gestures), and will
recognize (and also complete) as soon as a drag on a draggable actor should
start. As soon as that happens, the existing code in dnd.js takes over,
aquiring a (more exclusive) seat grab and handling things from there. This
porting of dnd.js will be done in the next commit.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2853>

js/ui/swipeTracker.js
src/st/meson.build
src/st/st-dnd-start-gesture.c [new file with mode: 0644]
src/st/st-dnd-start-gesture.h [new file with mode: 0644]

index f95574c3b149e52812bec8e462c92b7e1a2871d8..82edef29eaa83e9ef7e7c7707765eefcdae4de59 100644 (file)
@@ -574,7 +574,7 @@ export const SwipeTracker = GObject.registerClass({
     }
 
     _updatePanGesture(panGesture) {
-        const deltaVec = panGesture.get_delta();
+        const deltaVec = panGesture.get_delta_abs();
         const delta = this.orientation === Clutter.Orientation.HORIZONTAL
             ? -deltaVec.get_x()
             : -deltaVec.get_y();
index 5279455d8dfac919796138ba76d6b747d0fb3441..5fb742b259a270311a567f25fe29df213b57ffce 100644 (file)
@@ -6,6 +6,7 @@ st_headers = [
   'st-box-layout.h',
   'st-button.h',
   'st-clipboard.h',
+  'st-dnd-start-gesture.h',
   'st-drawing-area.h',
   'st-entry.h',
   'st-focus-manager.h',
@@ -119,6 +120,7 @@ st_sources = [
   'st-box-layout.c',
   'st-button.c',
   'st-clipboard.c',
+  'st-dnd-start-gesture.c',
   'st-drawing-area.c',
   'st-entry.c',
   'st-focus-manager.c',
diff --git a/src/st/st-dnd-start-gesture.c b/src/st/st-dnd-start-gesture.c
new file mode 100644 (file)
index 0000000..0e830e0
--- /dev/null
@@ -0,0 +1,381 @@
+#include "config.h"
+
+#include <clutter/clutter.h>
+
+#include "st-dnd-start-gesture.h"
+
+#include "st-theme-context.h"
+#include "st-settings.h"
+
+typedef struct _StDndStartGesture StDndStartGesture;
+typedef struct _StDndStartGesturePrivate StDndStartGesturePrivate;
+
+struct _StDndStartGesture
+{
+  ClutterGesture parent;
+};
+
+struct _StDndStartGesturePrivate
+{
+  gboolean drag_threshold_ignored;
+  ClutterEvent *point_begin_event;
+  ClutterEvent *drag_triggering_event;
+
+  gboolean manual_mode;
+  guint32 timeout_threshold_ms;
+};
+
+enum
+{
+  PROP_0,
+
+  PROP_MANUAL_MODE,
+  PROP_TIMEOUT_THRESHOLD,
+
+  PROP_LAST
+};
+
+static GParamSpec *obj_props[PROP_LAST] = { NULL, };
+
+G_DEFINE_TYPE_WITH_PRIVATE (StDndStartGesture, st_dnd_start_gesture,
+                            CLUTTER_TYPE_GESTURE);
+
+static gboolean
+should_handle_sequence (ClutterGesture     *gesture,
+                        const ClutterEvent *sequence_begin_event)
+{
+  ClutterEventType event_type = clutter_event_type (sequence_begin_event);
+
+  if (event_type == CLUTTER_BUTTON_PRESS ||
+      event_type == CLUTTER_TOUCH_BEGIN)
+    return TRUE;
+
+  return FALSE;
+}
+
+static void
+maybe_start_drag (StDndStartGesture *self,
+                  unsigned int       point)
+{
+  StDndStartGesturePrivate *priv =
+    st_dnd_start_gesture_get_instance_private (self);
+  const ClutterEvent *event;
+  graphene_point_t begin_coords, coords;
+  int drag_threshold;
+  ClutterStage *stage;
+  StThemeContext *theme_context;
+
+  event = clutter_gesture_get_point_event (CLUTTER_GESTURE (self), point);
+
+  clutter_gesture_get_point_begin_coords_abs (CLUTTER_GESTURE (self), point, &begin_coords);
+  clutter_gesture_get_point_coords_abs (CLUTTER_GESTURE (self), point, &coords);
+
+  if (priv->drag_threshold_ignored)
+      return;
+
+  g_object_get (G_OBJECT (st_settings_get ()),
+                "drag-threshold", &drag_threshold,
+                NULL);
+
+  stage = CLUTTER_STAGE (clutter_actor_get_stage (clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (self))));
+  theme_context = st_theme_context_get_for_stage (stage);
+  drag_threshold *= st_theme_context_get_scale_factor (theme_context);
+
+  if (ABS (coords.x - begin_coords.x) > drag_threshold ||
+      ABS (coords.y - begin_coords.y) > drag_threshold)
+    {
+      ClutterInputDeviceType device_type;
+      gboolean is_pointer_or_touchpad;
+      guint32 ellapsed_time;
+
+      device_type =
+        clutter_input_device_get_device_type (clutter_event_get_source_device (event));
+      is_pointer_or_touchpad =
+        device_type == CLUTTER_POINTER_DEVICE ||
+        device_type == CLUTTER_TOUCHPAD_DEVICE;
+      ellapsed_time =
+        clutter_event_get_time (event) - clutter_event_get_time (priv->point_begin_event);
+
+      /* Pointer devices (e.g. mouse) start the drag immediately */
+      if (is_pointer_or_touchpad || ellapsed_time > priv->timeout_threshold_ms)
+        st_dnd_start_gesture_start_drag (self, event);
+      else
+        priv->drag_threshold_ignored = true;
+    }
+}
+
+static void
+point_began (ClutterGesture *gesture,
+             unsigned int    point)
+{
+  StDndStartGesture *self = ST_DND_START_GESTURE (gesture);
+  StDndStartGesturePrivate *priv =
+    st_dnd_start_gesture_get_instance_private (self);
+  const ClutterEvent *event;
+
+  if (clutter_gesture_get_n_points (gesture) > 1)
+    {
+      clutter_gesture_set_state (gesture, CLUTTER_GESTURE_STATE_CANCELLED);
+      return;
+    }
+
+  event = clutter_gesture_get_point_event (gesture, point);
+
+  priv->point_begin_event = clutter_event_copy (event);
+  priv->drag_threshold_ignored = FALSE;
+
+  if (!priv->manual_mode &&
+      clutter_gesture_get_state (gesture) == CLUTTER_GESTURE_STATE_POSSIBLE)
+    maybe_start_drag (self, point);
+}
+
+static void
+point_moved (ClutterGesture *gesture,
+             unsigned int    point)
+{
+  StDndStartGesture *self = ST_DND_START_GESTURE (gesture);
+  StDndStartGesturePrivate *priv =
+    st_dnd_start_gesture_get_instance_private (self);
+
+  if (!priv->manual_mode &&
+      clutter_gesture_get_state (gesture) == CLUTTER_GESTURE_STATE_POSSIBLE)
+    maybe_start_drag (self, point);
+}
+
+static void
+point_ended (ClutterGesture *gesture,
+             unsigned int    point)
+{
+  if (clutter_gesture_get_state (gesture) == CLUTTER_GESTURE_STATE_POSSIBLE &&
+      clutter_gesture_get_n_points (gesture) == 1)
+    {
+      /* All points were removed and we're still in POSSIBLE, this means
+       * we're in manual mode and nobody told us to start the drag.
+       */
+      clutter_gesture_set_state (gesture, CLUTTER_GESTURE_STATE_CANCELLED);
+    }
+}
+
+static void
+state_changed (ClutterGesture      *gesture,
+               ClutterGestureState  old_state,
+               ClutterGestureState  new_state)
+{
+  StDndStartGesture *self = ST_DND_START_GESTURE (gesture);
+  StDndStartGesturePrivate *priv =
+    st_dnd_start_gesture_get_instance_private (self);
+
+  if (new_state == CLUTTER_GESTURE_STATE_WAITING)
+      {
+        g_clear_pointer (&priv->drag_triggering_event, clutter_event_free);
+        g_clear_pointer (&priv->point_begin_event, clutter_event_free);
+      }
+}
+
+static void
+st_dnd_start_gesture_set_property (GObject      *gobject,
+                                   unsigned int  prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  StDndStartGesture *self = ST_DND_START_GESTURE (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_MANUAL_MODE:
+      st_dnd_start_gesture_set_manual_mode (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_TIMEOUT_THRESHOLD:
+      st_dnd_start_gesture_set_timeout_threshold (self, g_value_get_uint (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+st_dnd_start_gesture_get_property (GObject      *gobject,
+                                   unsigned int  prop_id,
+                                   GValue       *value,
+                                   GParamSpec   *pspec)
+{
+  StDndStartGesture *self = ST_DND_START_GESTURE (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_MANUAL_MODE:
+      g_value_set_boolean (value, st_dnd_start_gesture_get_manual_mode (self));
+      break;
+
+    case PROP_TIMEOUT_THRESHOLD:
+      g_value_set_uint (value, st_dnd_start_gesture_get_timeout_threshold (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+st_dnd_start_gesture_init (StDndStartGesture *self)
+{
+}
+
+static void
+st_dnd_start_gesture_class_init (StDndStartGestureClass *klass)
+{
+  ClutterGestureClass *gesture_class = CLUTTER_GESTURE_CLASS (klass);
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gesture_class->should_handle_sequence = should_handle_sequence;
+  gesture_class->point_began = point_began;
+  gesture_class->point_moved = point_moved;
+  gesture_class->point_ended = point_ended;
+  gesture_class->state_changed = state_changed;
+
+  gobject_class->set_property = st_dnd_start_gesture_set_property;
+  gobject_class->get_property = st_dnd_start_gesture_get_property;
+
+  obj_props[PROP_MANUAL_MODE] =
+    g_param_spec_boolean ("manual-mode", NULL, NULL,
+                          FALSE,
+                          G_PARAM_READWRITE |
+                          G_PARAM_STATIC_STRINGS |
+                          G_PARAM_EXPLICIT_NOTIFY);
+
+  obj_props[PROP_TIMEOUT_THRESHOLD] =
+    g_param_spec_uint ("timeout-threshold", NULL, NULL,
+                       0, G_MAXUINT, 0,
+                       G_PARAM_READWRITE |
+                       G_PARAM_STATIC_STRINGS |
+                       G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
+}
+
+void
+st_dnd_start_gesture_start_drag (StDndStartGesture  *self,
+                                 const ClutterEvent *start_event)
+{
+  StDndStartGesturePrivate *priv;
+
+  g_return_if_fail (ST_IS_DND_START_GESTURE (self));
+
+  priv = st_dnd_start_gesture_get_instance_private (self);
+
+  if (clutter_gesture_get_n_points (CLUTTER_GESTURE (self)) != 1)
+    return;
+
+  if (clutter_gesture_get_state (CLUTTER_GESTURE (self)) == CLUTTER_GESTURE_STATE_POSSIBLE)
+    {
+      if (start_event)
+        priv->drag_triggering_event = clutter_event_copy (start_event);
+
+      clutter_gesture_set_state (CLUTTER_GESTURE (self), CLUTTER_GESTURE_STATE_COMPLETED);
+    }
+}
+
+/**
+ * st_dnd_start_gesture_get_point_begin_event:
+ * @self: The gesture.
+ *
+ * Returns the first #ClutterEvent of the dragging sequence.
+ *
+ * Returns: (transfer none): A #ClutterEvent
+ */
+const ClutterEvent *
+st_dnd_start_gesture_get_point_begin_event (StDndStartGesture *self)
+{
+  StDndStartGesturePrivate *priv;
+
+  g_return_val_if_fail (ST_IS_DND_START_GESTURE (self), NULL);
+
+  priv = st_dnd_start_gesture_get_instance_private (self);
+
+  return priv->point_begin_event;
+}
+
+/**
+ * st_dnd_start_gesture_get_drag_triggering_event:
+ * @self: The gesture.
+ *
+ * Returns the #ClutterEvent of the dragging sequence that actually triggered
+ * the drag.
+ *
+ * Returns: (transfer none): A #ClutterEvent
+ */
+const ClutterEvent *
+st_dnd_start_gesture_get_drag_triggering_event (StDndStartGesture *self)
+{
+  StDndStartGesturePrivate *priv;
+
+  g_return_val_if_fail (ST_IS_DND_START_GESTURE (self), NULL);
+
+  priv = st_dnd_start_gesture_get_instance_private (self);
+
+  return priv->drag_triggering_event;
+}
+
+gboolean
+st_dnd_start_gesture_get_manual_mode (StDndStartGesture *self)
+{
+  StDndStartGesturePrivate *priv;
+
+  g_return_val_if_fail (ST_IS_DND_START_GESTURE (self), FALSE);
+
+  priv = st_dnd_start_gesture_get_instance_private (self);
+
+  return priv->manual_mode;
+}
+
+void
+st_dnd_start_gesture_set_manual_mode (StDndStartGesture *self,
+                                      gboolean           manual_mode)
+{
+  StDndStartGesturePrivate *priv;
+
+  g_return_if_fail (ST_IS_DND_START_GESTURE (self));
+
+  priv = st_dnd_start_gesture_get_instance_private (self);
+
+  if (priv->manual_mode == manual_mode)
+    return;
+
+  priv->manual_mode = manual_mode;
+
+  g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_MANUAL_MODE]);
+}
+
+guint32
+st_dnd_start_gesture_get_timeout_threshold (StDndStartGesture *self)
+{
+  StDndStartGesturePrivate *priv;
+
+  g_return_val_if_fail (ST_IS_DND_START_GESTURE (self), 0);
+
+  priv = st_dnd_start_gesture_get_instance_private (self);
+
+  return priv->timeout_threshold_ms;
+}
+
+void
+st_dnd_start_gesture_set_timeout_threshold (StDndStartGesture *self,
+                                            uint32_t           timeout_threshold_ms)
+{
+  StDndStartGesturePrivate *priv;
+
+  g_return_if_fail (ST_IS_DND_START_GESTURE (self));
+
+  priv = st_dnd_start_gesture_get_instance_private (self);
+
+  if (priv->timeout_threshold_ms == timeout_threshold_ms)
+    return;
+
+  priv->timeout_threshold_ms = timeout_threshold_ms;
+
+  g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_TIMEOUT_THRESHOLD]);
+}
diff --git a/src/st/st-dnd-start-gesture.h b/src/st/st-dnd-start-gesture.h
new file mode 100644 (file)
index 0000000..2e3cf80
--- /dev/null
@@ -0,0 +1,37 @@
+#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
+#error "Only <st/st.h> can be included directly.h"
+#endif
+
+#pragma once
+
+#include <clutter/clutter.h>
+
+#include <st/st-types.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_DND_START_GESTURE (st_dnd_start_gesture_get_type ())
+G_DECLARE_FINAL_TYPE (StDndStartGesture, st_dnd_start_gesture,
+                      ST, DND_START_GESTURE, ClutterGesture)
+
+void st_dnd_start_gesture_start_drag (StDndStartGesture  *self,
+                                      const ClutterEvent *start_event);
+
+void st_dnd_start_gesture_get_drag_coords (StDndStartGesture *self,
+                                           graphene_point_t  *coords_out);
+
+const ClutterEvent * st_dnd_start_gesture_get_point_begin_event (StDndStartGesture *self);
+
+const ClutterEvent * st_dnd_start_gesture_get_drag_triggering_event (StDndStartGesture *self);
+
+gboolean st_dnd_start_gesture_get_manual_mode (StDndStartGesture *self);
+
+void st_dnd_start_gesture_set_manual_mode (StDndStartGesture *self,
+                                           gboolean           manual_mode);
+
+guint32 st_dnd_start_gesture_get_timeout_threshold (StDndStartGesture *self);
+
+void st_dnd_start_gesture_set_timeout_threshold (StDndStartGesture *self,
+                                                 uint32_t           timeout_threshold_ms);
+
+G_END_DECLS